package net.aiion.weave;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import net.aiion.weave.impl.ui.BindableFactory;
import net.aiion.weave.impl.ui.binder.BinderException;
import net.aiion.weave.spi.bytecode.BytecodeStore;
import net.aiion.weave.spi.cards.Card;
import net.aiion.weave.spi.cards.CardContext;
import net.aiion.weave.spi.cards.CardException;
import net.aiion.weave.spi.cards.expressions.CardExpressionResolverService;
import net.aiion.weave.spi.conversions.ConverterService;
import net.aiion.weave.spi.injection.annotations.Inject;
import net.aiion.weave.spi.naming.Context;
import net.aiion.weave.spi.naming.Contextual;
import net.aiion.weave.spi.painters.PaintBrush;
import net.aiion.weave.spi.painters.PainterService;
import net.aiion.weave.spi.painters.stream.StreamPaintBrush;
import net.aiion.weave.spi.ui.Bindable;
import net.aiion.weave.spi.ui.Component;
import net.aiion.weave.spi.ui.Container;
import net.aiion.weave.spi.xml.XmlGenerator;
import net.aiion.weave.spi.xml.XmlQualifiedName;
import net.aiion.weave.spi.xml.events.AttributeXmlEvent;
import net.aiion.weave.spi.xml.events.CharactersXmlEvent;
import net.aiion.weave.spi.xml.events.ElementEndXmlEvent;
import net.aiion.weave.spi.xml.events.ElementStartXmlEvent;

public class ComponentCards
{
}

class LabelCard implements Card
{
    @Inject
    private ConverterService converterService;

    public LabelCard() {
    }

    @Override
    public void processCard(CardContext aCardContext) {
        XmlGenerator xg = aCardContext.getXmlGenerator();
        CardExpressionResolverService cers = aCardContext.getExpressionResolverService();

        Object o;

        o = cers.readExpression("property", "visible");
        if (o != null && !Boolean.FALSE.equals(o)) {
            xg.startElement("http://www.w3.org/1999/xhtml", "label", "");
            xg.writeCharacters((String) converterService.convertValue(cers.readExpression("property", "value"),
                String.class));
            xg.endElement();
        }
    }
}

class TextFieldCard implements Card
{
    @Inject
    private ConverterService converterService;

    public TextFieldCard() {
    }

    @Override
    public void processCard(CardContext aCardContext) {
        XmlGenerator xg = aCardContext.getXmlGenerator();
        CardExpressionResolverService cers = aCardContext.getExpressionResolverService();

        Object o;

        o = cers.readExpression("property", "visible");
        if (o != null && !Boolean.FALSE.equals(o)) {
            o = cers.readExpression("property", "label");
            if (o != null && !Boolean.FALSE.equals(o)) {
                xg.startElement("http://www.w3.org/1999/xhtml", "label", "");
                xg.addAttribute("for", (String) converterService.convertValue(
                    cers.readExpression("property", "external-id"), String.class));
                xg.writeCharacters((String) converterService.convertValue(cers.readExpression("property", "label"),
                    String.class));
                xg.endElement();
            }

            xg.startElement("http://www.w3.org/1999/xhtml", "input", "");
            xg.addAttribute("type", "text");
            xg.addAttribute("id",
                (String) converterService.convertValue(cers.readExpression("property", "external-id"), String.class));
            xg.addAttribute("name",
                (String) converterService.convertValue(cers.readExpression("property", "external-id"), String.class));
            xg.addAttribute("value",
                (String) converterService.convertValue(cers.readExpression("property", "value"), String.class));
            xg.endElement();
        }
    }
}

class TextAreaCard implements Card
{
    @Inject
    private ConverterService converterService;

    public TextAreaCard() {
    }

    @Override
    public void processCard(CardContext aCardContext) {
        XmlGenerator xg = aCardContext.getXmlGenerator();
        CardExpressionResolverService cers = aCardContext.getExpressionResolverService();

        Object o;

        o = cers.readExpression("property", "visible");
        if (o != null && !Boolean.FALSE.equals(o)) {
            o = cers.readExpression("property", "label");
            if (o != null && !Boolean.FALSE.equals(o)) {
                xg.startElement("http://www.w3.org/1999/xhtml", "label", "");
                xg.addAttribute("for", (String) converterService.convertValue(
                    cers.readExpression("property", "external-id"), String.class));
                xg.writeCharacters((String) converterService.convertValue(cers.readExpression("property", "label"),
                    String.class));
                xg.endElement();
            }

            xg.startElement("http://www.w3.org/1999/xhtml", "input", "");
            xg.addAttribute("type", "text");

            // TODO Wie wird das momentan gemacht? WIrd schon der ConverterService dazu verwendet? -> Nein, noch nicht!
            // Bisher nur String.valueOf()
            xg.addAttribute("id",
                (String) converterService.convertValue(cers.readExpression("property", "external-id"), String.class));

            xg.addAttribute("name",
                (String) converterService.convertValue(cers.readExpression("property", "external-id"), String.class));
            xg.writeCharacters((String) converterService.convertValue(cers.readExpression("property", "value"),
                String.class));
            xg.endElement();
        }
    }
}

class HorizontalLayoutCard implements Card
{
    public HorizontalLayoutCard() {
    }

    @SuppressWarnings({
        "rawtypes", "unchecked"
    })
    @Override
    public void processCard(CardContext aCardContext) {
        XmlGenerator xg = aCardContext.getXmlGenerator();
        CardExpressionResolverService cers = aCardContext.getExpressionResolverService();

        Object o;
        Collection c;
        Object v;

        // <cards:if test="property:visible">
        o = cers.readExpression("property", "visible");
        if (o != null && !Boolean.FALSE.equals(o)) {
            // <div class="horizontal-layout">
            xg.startElement("http://www.w3.org/1999/xhtml", "div", "");
            xg.addAttribute("class", "horizontal-layout");

            // <cards:for each="variable:child" in="property:children">
            o = cers.readExpression("property", "children");
            if (o instanceof Collection) {
                c = (Collection) o;
            } else if (o.getClass().isArray()) {
                c = new ArrayList();
                int len = Array.getLength(o);
                int index = 0;
                while (index < len) {
                    c.add(Array.get(o, index));
                }
            } else {
                throw new CardException("Value 'property:children' is not an array or a collection");
            }
            Iterator it = c.iterator();
            while (it.hasNext()) {
                v = it.next();
                cers.writeExpression("variable", "child", v);

                // <div>
                xg.startElement("http://www.w3.org/1999/xhtml", "div", "");

                // <cards:component value="variable:child">
                PaintBrush p = ((PainterService) aCardContext.getContext().lookup(PainterService.class))
                    .paintComponent(cers.readExpression("variable", "child"));
                if (p instanceof StreamPaintBrush) {
                    xg.writeCharacters(((StreamPaintBrush) p).getOutputStream().toString(),
                        CharactersXmlEvent.TYPE_RAW_CHARACTER_DATA);
                }

                // </cards:component>

                // </div>
                xg.endElement();

                // </cards:for>
            }
            cers.writeExpression("variable", "child", null);

            // </div>
            xg.endElement();

            // </cards:if>
        }
    }
}

class SampleApplicationCard implements Card
{
    // TODO get it out somewhere
    private BindableFactory bindableFactory;

    public SampleApplicationCard() {
    }

    @Override
    public void processCard(CardContext aCardContext) {
    }

    // TODO Methoden für handleAttributeXmlEvent und handleCharactersXmlEvent vornotieren, handleElementEndXmlEvent

    Bindable runtimeBindable = null; // runtime variable
    Stack<Container> runtimeContainers = null; // runtime variable

    private void handleElementStartXmlEvent(ElementStartXmlEvent aElementStartXmlEvent) {
        Context RUNTIME_Context = null; // runtime variable

        XmlQualifiedName elementStartQualifiedName = aElementStartXmlEvent.getName(); // S!

        if (!staticBindables.isEmpty() && !(staticBindable instanceof Container)) { // S!
            throw new BinderException(String.format("Component of type '%s' does not accept any child components.",
                staticBindable.getClass().getName())); // S!
        } // S!

        staticBindable = bindableFactory.createBindable(elementStartQualifiedName.getNamespaceURI(),
            elementStartQualifiedName.getLocalName()); // S!

        runtimeBindable = bindableFactory.createBindable(RUNTIME_Context, elementStartQualifiedName.getNamespaceURI(),
            elementStartQualifiedName.getLocalName());
        if (!staticBindables.isEmpty() && staticBindable instanceof Component) { // S!
            runtimeContainers.peek().addComponent((Component) runtimeBindable);
        } // S!

        if (staticBindable instanceof Container) { // S!
            runtimeContainers.add((Container) runtimeBindable);
        } // S!
        staticBindables.add(staticBindable); // S!

        if (aElementStartXmlEvent.getAttribute("id") == null) { // S!
            currentId = componentIds.isEmpty()
                ? ""
                : String.valueOf(componentIds.size()); // S!
        } else { // S!
            currentId = aElementStartXmlEvent.getAttributeValue("id"); // S!
        } // S!
        componentIds.add(currentId); // S!

        if (RUNTIME_Context.lookup("SampleApplication/" + currentId) != null) {
            throw new BinderException("Cannot bind component of type '" + staticBindable.getClass().getName()
                + "' with id 'SampleApplication/test-id' as there is already a contextual value bound to this name.");
        }
        RUNTIME_Context.bind("SampleApplication/" + currentId, runtimeBindable);

        if (referencedIds.containsKey(currentId)) { // S!
            Map<String, String> waitingIds = referencedIds.remove(currentId); // S!
            for (String waitingId : waitingIds.keySet()) { // S!
                ((Bindable) RUNTIME_Context.lookup("SampleApplication/" + waitingId)).bindValue(
                    waitingIds.get(waitingId), runtimeBindable);
            } // S!
        } // S!
    }

    private void handleAttributeXmlEvent(AttributeXmlEvent aAttributeXmlEvent) {
        Context RUNTIME_Context = null; // runtime variable
        String attributeLocalName = aAttributeXmlEvent.getLocalName(); // S!
        String attributeValue = aAttributeXmlEvent.getValue(); // S!

        if ("id".equals(attributeLocalName)) { // S!
            return; // S!
        } // S!

        if (Component.class.isAssignableFrom(staticBindable.getValueType(attributeLocalName))) { // S!
            if (componentIds.contains(attributeValue)) { // S!
                runtimeBindable.bindValue(attributeLocalName,
                    RUNTIME_Context.lookup("SampleApplication/" + attributeValue)); // S? (partly)
            } else { // S!
                if (!referencedIds.containsKey(attributeValue)) { // S!
                    referencedIds.put(attributeValue, new HashMap<String, String>()); // S!
                } // S!
                referencedIds.get(attributeValue).put(currentId, attributeLocalName); // S!
            } // S!
        } else { // S!
            runtimeBindable.bindValue(attributeLocalName, attributeValue);
        } // S!
    }

    private void handleCharactersXmlEvent(CharactersXmlEvent aCharactersXmlEvent) {
        if (contentBuilder == null) { // S!
            contentBuilder = new StringBuilder(); // S!
        } // S!
        contentBuilder.append("Test"); // S!
    }

    private void handleElementEndXmlEvent(ElementEndXmlEvent aElementEndXmlEvent) {
        if (contentBuilder != null) { // S!
            runtimeBindable.bindValue(contentBuilder.toString());
            contentBuilder = null; // S!
        } // S!

        if (staticBindables.pop() instanceof Container) { // S!
            runtimeContainers.pop();
        } // S!
    }

    StringBuilder contentBuilder; // S!
    String currentId; // S!
    Bindable staticBindable; // S!
    Set<String> componentIds = new HashSet<>(); // S!
    Map<String, Map<String, String>> referencedIds = new HashMap<>(); // S!

    Stack<Bindable> staticBindables;
}

interface TestInput extends Component
{
}
